package app.modules.security.business.service.resourcee;

import app.modules.common.constant.CommonLockConstants;
import app.modules.common.enums.BoolEnum;
import app.modules.security.business.service.SyncRbacCacheService;
import app.modules.security.constant.SecurityConstants;
import app.modules.security.constant.SecurityLockConstants;
import app.modules.security.dto.web.resource.WebResourceDTO;
import app.modules.security.dto.web.resource.WebResourceQueryDTO;
import app.modules.security.dto.web.resource.WebResourceSaveDTO;
import app.modules.security.dto.web.resource.WebResourceUpdateDTO;
import app.modules.security.enums.AccessPolicyEnum;
import app.modules.security.enums.ResourceTypeEnum;
import app.modules.security.persistence.dao.FunctionResourceRelationDAO;
import app.modules.security.persistence.dao.PermissionResourceRelationDAO;
import app.modules.security.persistence.dao.ResourceDAO;
import app.modules.security.util.conversion.ResourceConvertUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jasmine.framework.common.exception.DataNotFoundException;
import jasmine.framework.common.util.CheckUtil;
import jasmine.framework.common.util.CollectionUtil;
import jasmine.framework.common.util.I18nUtil;
import jasmine.framework.common.util.MapperUtil;
import jasmine.framework.lock.annotation.DistributedLock;
import jasmine.security.rbac.model.SecResource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collections;
import java.util.List;

/**
 * @author mh.z
 */
@Service
public class ResourceService {
    private ResourceDAO resourceDAO;
    private FunctionResourceRelationDAO functionResourceRelationDAO;
    private PermissionResourceRelationDAO permissionResourceRelationDAO;
    private SyncRbacCacheService syncRbacCacheService;

    public ResourceService(ResourceDAO resourceDAO, FunctionResourceRelationDAO functionResourceRelationDAO,
                           PermissionResourceRelationDAO permissionResourceRelationDAO,
                           SyncRbacCacheService syncRbacCacheService) {
        this.resourceDAO = resourceDAO;
        this.functionResourceRelationDAO = functionResourceRelationDAO;
        this.permissionResourceRelationDAO = permissionResourceRelationDAO;
        this.syncRbacCacheService = syncRbacCacheService;
    }

    /**
     * 保存资源
     *
     * @param saveDTO
     * @return
     */
    @DistributedLock(category = SecurityLockConstants.LOCK_CATEGORY_CRUD_RESOURCE, key = CommonLockConstants.LOCK_KEY_ALL)
    @Transactional(rollbackFor = Exception.class)
    public WebResourceDTO saveResource(WebResourceSaveDTO saveDTO) {
        CheckUtil.notNull(saveDTO, "saveDTO null");

        SecResource secResource = new SecResource();
        MapperUtil.mapFields(saveDTO, secResource);
        secResource.setResourceCategory(SecurityConstants.RESOURCE_CATEGORY_DEFAULT);
        // 保存记录
        resourceDAO.save(secResource);

        return getWebResourceDtoById(secResource.getId());
    }

    /**
     * 更新资源
     *
     * @param updateDTO
     * @return
     */
    @DistributedLock(category = SecurityLockConstants.LOCK_CATEGORY_CRUD_RESOURCE, key = CommonLockConstants.LOCK_KEY_ALL)
    @Transactional(rollbackFor = Exception.class)
    public WebResourceDTO updateResource(WebResourceUpdateDTO updateDTO) {
        CheckUtil.notNull(updateDTO, "updateDTO null");
        Long id = CheckUtil.notNull(updateDTO.getId(), "updateDTO.id null");

        SecResource secResource = resourceDAO.getById(id);
        if (secResource == null) {
            throw new DataNotFoundException(SecResource.class, id);
        }

        MapperUtil.mapFields(updateDTO, secResource);
        // 更新记录
        resourceDAO.strictUpdateById(secResource);

        // 同步缓存
        syncRbacCacheService.syncResourceWithRequestCache(SecResource.class, secResource);

        return getWebResourceDtoById(id);
    }

    /**
     * 删除资源
     *
     * @param recordId
     */
    @DistributedLock(category = SecurityLockConstants.LOCK_CATEGORY_CRUD_RESOURCE, key = CommonLockConstants.LOCK_KEY_ALL)
    @Transactional(rollbackFor = Exception.class)
    public void deleteRecordById(Long recordId) {
        CheckUtil.notNull(recordId, "recordId null");

        resourceDAO.deleteById(recordId);
        functionResourceRelationDAO.deleteByResourceId(recordId);
        permissionResourceRelationDAO.deleteByResourceId(recordId);

        // 同步缓存
        syncRbacCacheService.syncResourceWithRequestCache(SecResource.class, recordId);
    }

    /**
     * 分页查询资源
     *
     * @param queryDTO
     * @param page
     * @return
     */
    public List<WebResourceDTO> pageWebResourceDTOsByCond(WebResourceQueryDTO queryDTO, Page page) {
        CheckUtil.notNull(queryDTO, "queryDTO null");
        CheckUtil.notNull(page, "page null");

        List<WebResourceDTO> recordList = resourceDAO.pageWebResourceDTOsByCond(queryDTO, page);
        populateWebResourceDTOs(recordList);

        return recordList;
    }

    /**
     * 查找指定 ID 的记录
     *
     * @param id
     * @return
     */
    public WebResourceDTO getWebResourceDtoById(Long id) {
        CheckUtil.notNull(id, "id null");

        SecResource secResource = resourceDAO.getById(id);
        WebResourceDTO webResourceDTO = ResourceConvertUtil.secResource2WebResourceDTO(secResource);
        populateWebResourceDTOs(Collections.singletonList(webResourceDTO));

        return webResourceDTO;
    }

    /**
     * 填充数据
     *
     * @param records
     */
    public void populateWebResourceDTOs(List<WebResourceDTO> records) {
        if (CollectionUtil.isEmpty(records)) {
            return;
        }

        records.forEach((record) -> {
            ResourceTypeEnum resourceTypeEnum = ResourceTypeEnum.parseCode(record.getResourceType());
            record.setResourceTypeDesc(I18nUtil.getMessage(resourceTypeEnum));

            AccessPolicyEnum accessPolicyEnum = AccessPolicyEnum.parseCode(record.getAccessPolicy());
            record.setAccessPolicyDesc(I18nUtil.getMessage(accessPolicyEnum));

            BoolEnum boolEnum = BoolEnum.parseValue(record.getFrozenFlag());
            record.setFrozenFlagDesc(I18nUtil.getMessage(boolEnum));
        });
    }

}
